
/**
 * AppleIIGo
 * Video Maps class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 * Inspired by Jim Sather's "Understanding the Apple IIe"
 */

#include "VideoMaps.h"

VideoMaps::VideoMaps(VideoRenderer * videoRenderer) {
	this->videoRenderer = videoRenderer;
	initRenderMaps();
	initAddressMaps();
	initIsVideoModifiedMaps();
	
	initVideoModeMaps();
}

UINT32 ** VideoMaps::getVideoModeToRenderMap() {
	return videoModeToRenderMap;
}

UINT32 ** VideoMaps::getVideoModeToAddressMap() {
	return videoModeToAddressMap;
}

UINT8 ** VideoMaps::getVideoModeToIsVideoModifiedMap() {
	return videoModeToIsVideoModifiedMap;
}

void VideoMaps::initAppleIIRenderMap(UINT32 * renderMap,
									   UINT32 renderModeTop,
									   UINT32 renderModeBottom) {
	for (UINT32 scanlineIndex = 0; scanlineIndex < SCANLINE_END;
		 scanlineIndex++) {
		if (scanlineIndex < SCANLINE_APPLEII)
			renderMap[scanlineIndex] = RENDERMODE_BORDER;
		else if (scanlineIndex < SCANLINE_APPLEII_MIXED)
			renderMap[scanlineIndex] = renderModeTop;
		else if (scanlineIndex < SCANLINE_APPLEII_END)
			renderMap[scanlineIndex] = renderModeBottom;
		else
			renderMap[scanlineIndex] = RENDERMODE_BORDER;
	}
}

void VideoMaps::initRenderMap(UINT32 * renderMap,
								UINT32 scanlineStart,
								UINT32 scanlineEnd,
								UINT32 renderMode) {
	for (UINT32 scanlineIndex = 0; scanlineIndex < SCANLINE_END;
		 scanlineIndex++) {
		if (scanlineIndex < scanlineStart)
			renderMap[scanlineIndex] = RENDERMODE_BORDER;
		else if (scanlineIndex < scanlineEnd)
			renderMap[scanlineIndex] = renderMode;
		else
			renderMap[scanlineIndex] = RENDERMODE_BORDER;
	}
}

void VideoMaps::initRenderMaps() {
	initAppleIIRenderMap(renderMaps[RENDERMAP_TEXT],
						 RENDERMODE_TEXT, RENDERMODE_TEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DTEXT], 
						 RENDERMODE_DTEXT, RENDERMODE_DTEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_LORES], 
						 RENDERMODE_LORES, RENDERMODE_LORES);
	initAppleIIRenderMap(renderMaps[RENDERMAP_7MLORES], 
						 RENDERMODE_7MLORES, RENDERMODE_7MLORES);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DLORES], 
						 RENDERMODE_DLORES, RENDERMODE_DLORES);
	initAppleIIRenderMap(renderMaps[RENDERMAP_HIRES], 
						 RENDERMODE_HIRES, RENDERMODE_HIRES);
	initAppleIIRenderMap(renderMaps[RENDERMAP_7HIRES], 
						 RENDERMODE_7HIRES, RENDERMODE_7HIRES);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DHIRES], 
						 RENDERMODE_DHIRES, RENDERMODE_DHIRES);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DHIRESMONO], 
						 RENDERMODE_DHIRESMONOCHROME,
						 RENDERMODE_DHIRESMONOCHROME);
	initAppleIIRenderMap(renderMaps[RENDERMAP_LORESTEXT], 
						 RENDERMODE_LORES, RENDERMODE_TEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_LORESDTEXT], 
						 RENDERMODE_LORES, RENDERMODE_DTEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_7MLORESTEXT], 
						 RENDERMODE_7MLORES, RENDERMODE_TEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DLORESDTEXT], 
						 RENDERMODE_DLORES, RENDERMODE_DTEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_HIRESTEXT], 
						 RENDERMODE_HIRES, RENDERMODE_TEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_HIRESDTEXT], 
						 RENDERMODE_HIRES, RENDERMODE_DTEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_7HIRESTEXT], 
						 RENDERMODE_7HIRES, RENDERMODE_TEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DHIRESDTEXT], 
						 RENDERMODE_DHIRES, RENDERMODE_DTEXT);
	initAppleIIRenderMap(renderMaps[RENDERMAP_DHIRESMONODTEXT], 
						 RENDERMODE_DHIRESMONOCHROME, RENDERMODE_DTEXT);
	initRenderMap(renderMaps[RENDERMAP_SHIRES],	SCANLINE_APPLEII, 
				  SCANLINE_APPLEII + (VERT_SHIRES_END - VERT_ACTIVEVIDEO),
				  RENDERMODE_SHIRES);
	initRenderMap(renderMaps[RENDERMAP_VIDEOTERM], SCANLINE_VIDEOTERM,
				  SCANLINE_VIDEOTERM + SCANLINE_VIDEOTERM_NUM,
				  RENDERMODE_VIDEOTERM);
}

UINT32 VideoMaps::getShadowAddress(UINT32 scanlineIndex, UINT32 videoMode) {
	if (getBit(videoMode, VIDEOMODE_VIDEOTERM)) {
		if (scanlineIndex < SCANLINE_VIDEOTERM)
			return 0;
		else if (scanlineIndex < SCANLINE_VIDEOTERM_END)
			return SHADOWMEMORY_VIDEOTERM +
				((scanlineIndex - SCANLINE_VIDEOTERM) /
				VIDEOTERM_CHARHEIGHT * VIDEOTERM_PITCH);
		else
			return 0;
	}
	
	if (getBit(videoMode, VIDEOMODE_SHIRES)) {
		if (scanlineIndex < SCANLINE_APPLEII)
			return 0;
		else if (scanlineIndex < SCANLINE_SHIRES_END)
			return SHIRESDATA_ADDRESS + (scanlineIndex - SCANLINE_APPLEII) *
				SHIRES_PITCH;
		else
			return 0;
	}
	
	if (scanlineIndex < SCANLINE_APPLEII)
		return 0;
	else if (scanlineIndex < SCANLINE_APPLEII_END)
		return videoRenderer->getVideoScannerAddress(VERT_ACTIVEVIDEO +
			(scanlineIndex - SCANLINE_APPLEII), HORIZ_ACTIVEVIDEO, videoMode);
	else
		return 0;
}

void VideoMaps::initAddressMap(UINT32 * addressMap, UINT32 videoMode) {
	for (UINT32 scanlineIndex = 0; scanlineIndex < SCANLINE_END;
		 scanlineIndex++)
		addressMap[scanlineIndex] = getShadowAddress(scanlineIndex, videoMode);
}

void VideoMaps::initAddressMaps() {
	initAddressMap(addressMaps[ADDRESSMAP_TEXTPAGE1],
				   VIDEOMODE_TEXT);
	initAddressMap(addressMaps[ADDRESSMAP_TEXTPAGE2],
				   VIDEOMODE_TEXT | VIDEOMODE_PAGE2);
	initAddressMap(addressMaps[ADDRESSMAP_HIRESPAGE1],
				   VIDEOMODE_HIRES);
	initAddressMap(addressMaps[ADDRESSMAP_HIRESPAGE2],
				   VIDEOMODE_HIRES | VIDEOMODE_PAGE2);
	initAddressMap(addressMaps[ADDRESSMAP_HIRESTEXTPAGE1],
				   VIDEOMODE_MIXED | VIDEOMODE_HIRES);
	initAddressMap(addressMaps[ADDRESSMAP_HIRESTEXTPAGE2],
				   VIDEOMODE_MIXED | VIDEOMODE_HIRES | VIDEOMODE_PAGE2);
	initAddressMap(addressMaps[ADDRESSMAP_SHIRES],
				   VIDEOMODE_SHIRES);
	initAddressMap(addressMaps[ADDRESSMAP_VIDEOTERM],
				   VIDEOMODE_VIDEOTERM);
}

UINT8 VideoMaps::getIsVideoModifiedAddress(UINT32 scanlineIndex,
										   UINT32 address) {
	if (address == 0)
		return ISVIDEOMODIFIED_INVISIBLE;
	else if (address >= SHADOWMEMORY_VIDEOTERM)
		return ISVIDEOMODIFIED_TEXTLINE + 
		(scanlineIndex - SCANLINE_VIDEOTERM) / VIDEOTERM_CHARHEIGHT;
	else if ((address >= TEXTPAGE1_ADDRESS) &&
			 (address < TEXTPAGE2_ADDRESS_END))
		return ISVIDEOMODIFIED_TEXTLINE + 
		(scanlineIndex - SCANLINE_APPLEII) / APPLEII_CHARHEIGHT;
	else
		return scanlineIndex;
}

UINT32 VideoMaps::getPitch(UINT32 videoMode) {
	if (getBit(videoMode, VIDEOMODE_VIDEOTERM))
		return VIDEOTERM_PITCH;
	if (getBit(videoMode, VIDEOMODE_SHIRES))
		return SHIRES_PITCH;
	return APPLEII_PITCH;
}

void VideoMaps::initIsVideoModifiedMap(UINT8 * isVideoModifiedMap,
									   UINT32 videoMode) {
	UINT32 pitch = getPitch(videoMode);
	bool isDHires = getBit(videoMode, VIDEOMODE_DHIRES);
	
	for (UINT32 index = 0; index < SHADOWMEMORY_END; index++)
		isVideoModifiedMap[index] = ISVIDEOMODIFIED_INVISIBLE;
	
	for (UINT32 scanlineIndex = 0; scanlineIndex < SCANLINE_END;
		 scanlineIndex++) {
		UINT32 address = getShadowAddress(scanlineIndex, videoMode);
		UINT32 isVideoModifiedAddress = getIsVideoModifiedAddress(scanlineIndex,
																  address);
		
		if (isVideoModifiedAddress == ISVIDEOMODIFIED_INVISIBLE)
			continue;
		
		if (!getBit(videoMode, VIDEOMODE_SHIRES) && (address & 0x6000)) {
			// Known bug: Write to $2027 and delayed bit at $2028 does not
			// update the leftmost pixel at $2028
			if ((address & 0x7f) == 0)
				isVideoModifiedMap[address | 0x7f] = isVideoModifiedAddress;
		}
		
		for (UINT32 index = 0; index < pitch; index++) {
			isVideoModifiedMap[address] = isVideoModifiedAddress;
			if (isDHires)
				isVideoModifiedMap[address | BANK_BYTENUM] = 
					isVideoModifiedAddress;
			address++;
		}
	}
}

void VideoMaps::initIsVideoModifiedMaps() {
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_TEXTPAGE1],
		VIDEOMODE_TEXT);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_TEXTPAGE2],
		VIDEOMODE_TEXT | VIDEOMODE_PAGE2);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESPAGE1],
		VIDEOMODE_HIRES);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESPAGE2],
		VIDEOMODE_HIRES | VIDEOMODE_PAGE2);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESTEXTPAGE1],
		VIDEOMODE_HIRES | VIDEOMODE_MIXED);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESTEXTPAGE2],
		VIDEOMODE_HIRES | VIDEOMODE_MIXED | VIDEOMODE_PAGE2);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DTEXTPAGE1],
		VIDEOMODE_DHIRES | VIDEOMODE_TEXT);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DTEXTPAGE2],
		VIDEOMODE_DHIRES | VIDEOMODE_TEXT | VIDEOMODE_PAGE2);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESPAGE1],
		VIDEOMODE_DHIRES | VIDEOMODE_HIRES);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESPAGE2],
		VIDEOMODE_DHIRES | VIDEOMODE_HIRES | VIDEOMODE_PAGE2);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESDTEXTPAGE1],
		VIDEOMODE_DHIRES | VIDEOMODE_HIRES | VIDEOMODE_MIXED);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESDTEXTPAGE2],
		VIDEOMODE_DHIRES | VIDEOMODE_HIRES | VIDEOMODE_MIXED | VIDEOMODE_PAGE2);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_SHIRES],
		VIDEOMODE_SHIRES);
	initIsVideoModifiedMap(
		isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_VIDEOTERM],
		VIDEOMODE_VIDEOTERM);
}

UINT32 * VideoMaps::getRenderMap(UINT32 videoMode) {
	bool isText = getBit(videoMode, VIDEOMODE_TEXT);
	bool isMixed = getBit(videoMode, VIDEOMODE_MIXED);
	bool isHires = getBit(videoMode, VIDEOMODE_HIRES);
	bool is80Col = getBit(videoMode, VIDEOMODE_80COL);
	bool isDHires = getBit(videoMode, VIDEOMODE_DHIRES);
	bool isDHiresMonochrome = getBit(videoMode, VIDEOMODE_DHIRESMONOCHROME);
	bool isSHires = getBit(videoMode, VIDEOMODE_SHIRES);
	bool isVideoterm = getBit(videoMode, VIDEOMODE_VIDEOTERM);
	
	if (isVideoterm)
		return renderMaps[RENDERMAP_VIDEOTERM];
	else if (isSHires)
		return renderMaps[RENDERMAP_SHIRES];
	else if (isText) {
		if (!is80Col)
			return renderMaps[RENDERMAP_TEXT];
		else
			return renderMaps[RENDERMAP_DTEXT];
	} else if (!isMixed) {
		if (!isHires) {
			if (!isDHires)
				return renderMaps[RENDERMAP_LORES];
			else if (!is80Col)
				return renderMaps[RENDERMAP_7MLORES];
			else
				return renderMaps[RENDERMAP_DLORES];
		} else {
			if (!isDHires)
				return renderMaps[RENDERMAP_HIRES];
			else if (!is80Col)
				return renderMaps[RENDERMAP_7HIRES];
			else if (!isDHiresMonochrome)
				return renderMaps[RENDERMAP_DHIRES];
			else
				return renderMaps[RENDERMAP_DHIRESMONO];
		}
	} else {
		if (!isHires) {
			if (!isDHires) {
				if (!is80Col)
					return renderMaps[RENDERMAP_LORESTEXT];
				else
					return renderMaps[RENDERMAP_LORESDTEXT];
			} else {
				if (!is80Col)
					return renderMaps[RENDERMAP_7MLORESTEXT];
				else
					return renderMaps[RENDERMAP_DLORESDTEXT];
			}
		} else {
			if (!isDHires) {
				if (!is80Col)
					return renderMaps[RENDERMAP_HIRESTEXT];
				else
					return renderMaps[RENDERMAP_HIRESDTEXT];
			} else {
				if (!is80Col)
					return renderMaps[RENDERMAP_7HIRESTEXT];
				else if (!isDHiresMonochrome)
					return renderMaps[RENDERMAP_DHIRESDTEXT];
				else
					return renderMaps[RENDERMAP_DHIRESMONODTEXT];
			}
		}
	}
}

UINT32 * VideoMaps::getAddressMap(UINT32 videoMode) {
	bool isMixed = getBit(videoMode, VIDEOMODE_MIXED);
	bool isPage2Selected = getBit(videoMode, VIDEOMODE_PAGE2) &&
	!getBit(videoMode, VIDEOMODE_80STORE);
	bool isHiresVisible = !getBit(videoMode, VIDEOMODE_TEXT) &&
	getBit(videoMode, VIDEOMODE_HIRES);
    bool isSHires = getBit(videoMode, VIDEOMODE_SHIRES);
	bool isVideoterm = getBit(videoMode, VIDEOMODE_VIDEOTERM);
	
	if (isVideoterm)
		return addressMaps[ADDRESSMAP_VIDEOTERM];
	else if (isSHires)
		return addressMaps[ADDRESSMAP_SHIRES];
	else if (!isHiresVisible) {
		if (isPage2Selected)
			return addressMaps[ADDRESSMAP_TEXTPAGE2];
		else
			return addressMaps[ADDRESSMAP_TEXTPAGE1];
	} else if (isMixed) {
		if (isPage2Selected)
			return addressMaps[ADDRESSMAP_HIRESTEXTPAGE2];
		else
			return addressMaps[ADDRESSMAP_HIRESTEXTPAGE1];
	} else {
		if (isPage2Selected)
			return addressMaps[ADDRESSMAP_HIRESPAGE2];
		else
			return addressMaps[ADDRESSMAP_HIRESPAGE1];
	}
}

UINT8 * VideoMaps::getIsVideoModifiedMap(UINT32 videoMode) {
	bool isMixed = getBit(videoMode, VIDEOMODE_MIXED);
	bool isPage2Selected = getBit(videoMode, VIDEOMODE_PAGE2) &&
	!getBit(videoMode, VIDEOMODE_80STORE);
	bool isHiresVisible = !getBit(videoMode, VIDEOMODE_TEXT) &&
	getBit(videoMode, VIDEOMODE_HIRES);
	bool is80Col = getBit(videoMode, VIDEOMODE_80COL);
	bool isSHires = getBit(videoMode, VIDEOMODE_SHIRES);
	bool isVideoterm = getBit(videoMode, VIDEOMODE_VIDEOTERM);
	
	if (isVideoterm)
		return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_VIDEOTERM];
	else if (isSHires)
		return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_SHIRES];
	else if (!isHiresVisible) {
		if (is80Col) {
			if (isPage2Selected)
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DTEXTPAGE2];
			else
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DTEXTPAGE1];
		} else {
			if (isPage2Selected)
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_TEXTPAGE2];
			else
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_TEXTPAGE1];
		}
	} else if (isMixed) {
		if (is80Col) {
			if (isPage2Selected)
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESDTEXTPAGE2];
			else
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESDTEXTPAGE1];
		} else {
			if (isPage2Selected)
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESTEXTPAGE2];
			else
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESTEXTPAGE1];
		}
	} else {
		if (is80Col) {
			if (isPage2Selected)
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESPAGE2];
			else
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_DHIRESPAGE1];
		} else {
			if (isPage2Selected)
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESPAGE2];
			else
				return isVideoModifiedMaps[ISVIDEOMODIFIEDMAP_HIRESPAGE1];
		}
	}
}

void VideoMaps::initVideoModeMaps() {
	for (UINT32 videoMode = 0; videoMode < VIDEOMODE_END; videoMode++) {
		videoModeToRenderMap[videoMode] = getRenderMap(videoMode);
		videoModeToAddressMap[videoMode] = getAddressMap(videoMode);
		videoModeToIsVideoModifiedMap[videoMode] =
			getIsVideoModifiedMap(videoMode);
	}
}
